/*
 * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"

#include "esp_lcd_ili9341.h"
#include "ui/ui.h"


static const char *TAG = "st7789";

// Using SPI2 in the example
#define LCD_HOST  SPI2_HOST
#define EXAMPLE_LCD_H_RES              240
#define EXAMPLE_LCD_V_RES              240
#define EXAMPLE_LCD_BIT_PER_PIXEL      16            /* LCD bit per pixel */

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ     (20 * 1000 * 1000)
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_SCLK           13
#define EXAMPLE_PIN_NUM_DATA0          15
#define EXAMPLE_PIN_NUM_LCD_DC         16
#define EXAMPLE_PIN_NUM_LCD_RST        17
#define EXAMPLE_PIN_NUM_LCD_CS         21
#define EXAMPLE_PIN_NUM_BK_LIGHT       14

#define EXAMPLE_LVGL_BUFFER_SIZE        EXAMPLE_LCD_H_RES * 50 * sizeof(lv_color_t)     /*!< LVGL buffer size */

#define EXAMPLE_LVGL_TICK_PERIOD_MS     2               /*!< LVGL tick period in ms */
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS  500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS  1
#define EXAMPLE_LVGL_TASK_STACK_SIZE    (8 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY      2

static SemaphoreHandle_t    lvgl_mux    = NULL;

static const ili9341_lcd_init_cmd_t lcd_init_cmds[] = {
        /* {cmd, { data }, data_size, delay_ms} */ 
        {0x11, (uint8_t []){0x00}, 1, 120},
        {0xB2, (uint8_t []){0x0B, 0x0B, 0x00, 0x33, 0x33}, 5, 0},
        {0xB7, (uint8_t []){0x11}, 1, 0},
        {0xBB, (uint8_t []){0x2F}, 1, 0},
        {0xC0, (uint8_t []){0x2C}, 1, 0},
        {0xC2, (uint8_t []){0x01}, 1, 0},
        {0xC3, (uint8_t []){0x0D}, 1, 0},
        {0xC4, (uint8_t []){0x20}, 1, 0},
        {0xC6, (uint8_t []){0x13}, 1, 0},
        {0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0},
        {0xD6, (uint8_t []){0xA1}, 1, 0},
        {0xE0, (uint8_t []){0xF0, 0x04, 0x07, 0x09, 0x07, 0x13, 0x25, 0x33, 0x3C, 0x34, 0x10, 0x10, 0x29, 0x32}, 14, 0},
        {0xE1, (uint8_t []){0xF0, 0x05, 0x08, 0x0A, 0x09, 0x05, 0x25, 0x32, 0x3B, 0x3B, 0x17, 0x18, 0x2E, 0x37}, 14, 0},
        {0xE4, (uint8_t []){0x25, 0x00, 0x00}, 3, 0},
        {0x21, (uint8_t []){0x00}, 1, 0},
        {0x29, (uint8_t []){0x00}, 1, 0},
        {0x2A, (uint8_t []){0x00, 0x00, 0x00, 0xEF}, 4, 0},
        {0x2B, (uint8_t []){0x00, 0x14, 0x01, 0x2B}, 4, 0},
        {0x2C, (uint8_t []){0x00}, 1, 0},
};

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}

static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    /* Copy a buffer's content to a specific area of the display */
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

static void example_increase_lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);

}

static bool example_lvgl_lock(int timeout_ms)
{
    assert(lvgl_mux && "bsp_display_start must be called first");

    const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
    return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}

static void example_lvgl_unlock(void)
{
    assert(lvgl_mux && "bsp_display_start must be called first");
    xSemaphoreGive(lvgl_mux);
}

static void example_lvgl_port_task(void *arg)
{
    ESP_LOGI(TAG, "Starting LVGL task");
    ESP_LOGI(TAG, "Display LVGL UI");

    ui_init();

    uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
    while (1) {
        /* Lock the mutex due to the LVGL APIs are not thread-safe */
        if (example_lvgl_lock(-1)) {
            task_delay_ms = lv_timer_handler();
            /* Release the mutex */
            example_lvgl_unlock();
        }
        if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
        } else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
        }
        vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
    }
}

#include "../encoder/encoder.h"  
static void encoder_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data) {
    EncoderState *state = encoder_get_state(); // 获取当前编码器状态

    // 设置 LVGL 数据
    if (state->button == ENCODER_BUTTON_PRESSED) {
        data->state = LV_INDEV_STATE_PR;  // 按下
    } else {
        data->state = LV_INDEV_STATE_REL; // 释放
    }

    data->enc_diff = state->direction == ENCODER_ROTATE_FORWARD ? 1 :
                     state->direction == ENCODER_ROTATE_BACKWARD ? -1 : 0;

    // 返回 false 表示没有更多数据
    encoder_get_event_flag(); // 清除事件标志
    ESP_LOGI("ENCODER", "enc_diff: %d, state: %s",
         data->enc_diff,
         data->state == LV_INDEV_STATE_PR ? "Pressed" : "Released");
}

void lvgl_main(void)
{
    static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
    static lv_disp_drv_t disp_drv;      // contains callback functions


    /* =============== Initialize SPI bus =============== */
    ESP_LOGI(TAG, "Turn off LCD backlight");
    gpio_config_t bk_gpio_config = {
        .mode = GPIO_MODE_OUTPUT,
        .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
    };
    ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));

    ESP_LOGI(TAG, "Initialize SPI bus");
    const spi_bus_config_t buscfg = ILI9341_PANEL_BUS_SPI_CONFIG(EXAMPLE_PIN_NUM_SCLK, EXAMPLE_PIN_NUM_DATA0,
                                    EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t));
    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));

    ESP_LOGI(TAG, "Install panel IO");
    esp_lcd_panel_io_handle_t io_handle = NULL;
    const esp_lcd_panel_io_spi_config_t io_config = ILI9341_PANEL_IO_SPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS, EXAMPLE_PIN_NUM_LCD_DC,
            example_notify_lvgl_flush_ready, &disp_drv);
    // Attach the LCD to the SPI bus
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));


    ESP_LOGI(TAG, "Install ILI9341 panel driver");
    ESP_LOGI(TAG, "Install ili9341 panel driver");

    esp_lcd_panel_handle_t panel_handle = NULL;
    ili9341_vendor_config_t vendor_config = {
        .init_cmds      = lcd_init_cmds,
        .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(ili9341_lcd_init_cmd_t),
    };
    const esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
        .rgb_endian     = LCD_RGB_ENDIAN_RGB,
        .bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL,
        .vendor_config  = &vendor_config, 
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(io_handle, &panel_config, &panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    ESP_LOGI(TAG, "Turn on LCD backlight");
    gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);


    /* =============== Initialize LVGL =============== */
    ESP_LOGI(TAG, "Initialize LVGL");

    lv_init();
    /* Alloc draw buffers used by LVGL */
    /* It's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */
    lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(EXAMPLE_LVGL_BUFFER_SIZE, MALLOC_CAP_DMA);
    assert(buf1);
    lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(EXAMPLE_LVGL_BUFFER_SIZE, MALLOC_CAP_DMA);
    assert(buf2);
    /* Initialize LVGL draw buffers */
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 50);

    /* =============== Register display driver to LVGL =============== */
    ESP_LOGI(TAG, "Register display driver to LVGL");

    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res    = EXAMPLE_LCD_H_RES;
    disp_drv.ver_res    = EXAMPLE_LCD_V_RES;
    disp_drv.flush_cb   = example_lvgl_flush_cb;
    disp_drv.draw_buf   = &disp_buf;
    disp_drv.user_data  = panel_handle;
    lv_disp_t *disp     = lv_disp_drv_register(&disp_drv);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    
    /* =============== Register encoder to LVGL =============== */
    ESP_LOGI(TAG, "Register encoder to LVGL");
    encoder_init(18, 3, 2); // 替换为实际引脚
    // 初始化输入设备驱动
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb = encoder_read_cb;

    // 注册设备
    lv_indev_t *indev_encoder = lv_indev_drv_register(&indev_drv);

    // 可选：设置 LVGL 的焦点对象
    lv_group_t *group = lv_group_create();
    if (!group) {
        ESP_LOGE("ERROR", "Failed to create LVGL group!");
        return;
    }
    lv_indev_set_group(indev_encoder, group);
    // 设置焦点样式
    lv_group_set_default(group);

    /* Tick interface for LVGL (using esp_timer to generate 2ms periodic event) */
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback   = &example_increase_lvgl_tick,
        .name       = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

    /* =============== Create LVGL Task =============== */
    ESP_LOGI(TAG, "Run LVGL Task");

    lvgl_mux = xSemaphoreCreateMutex();
    assert(lvgl_mux);
    xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
}
